// Copyright (c) 2016  David Muse
// All rights reserved.
//
// This code is marked CONFIDENTIAL.

#include <rudiments/commandline.h>
#include <rudiments/charstring.h>
#include <rudiments/stdio.h>
#include <rudiments/randomnumber.h>
#include <rudiments/datetime.h>
#include <rudiments/process.h>
#include <rudiments/snooze.h>

filedescriptor	*debugdest;

unsigned char lrc(const unsigned char *str,
			bool hasextra, unsigned char extra) {
	unsigned char	lrc=0;
	for (const unsigned char *c=str; *c; c++) {
		lrc=(lrc+*c)&0xff;
	}
	if (hasextra) {
		lrc=(lrc+extra)&0xff;
	}
	lrc=(((lrc^0xff)+1)&0xff);
	lrc=(lrc<128)?lrc:(lrc-128);
	return lrc;
}

bool generateNameKey(const char *name, bool debug) {

	if (debug) {
		debugdest->printf("======================="
					"=======================\n");
		debugdest->printf("name: %s\n",name);
	}

	// get lrc of product name
	unsigned char	lrcname=lrc((const unsigned char *)name,false,0);
	if (debug) {
		debugdest->printf("lrc of \"%s\": %03d\n",name,lrcname);
	}

	// get lrc as 3 characters
	unsigned char	lrc1=(lrcname%10)+'0';
	lrcname/=10;
	unsigned char	lrc10=(lrcname%10)+'0';
	lrcname/=10;
	unsigned char	lrc100=(lrcname%10)+'0';
	if (debug) {
		debugdest->printf("lrc of \"%s\" (as 3 characters): %c %c %c\n",
							name,lrc100,lrc10,lrc1);
	}

	// init the random number generator
	randomnumber	r;
	r.setSeed(randomnumber::getSeed());
	int32_t		val;

	// generate the first 3 parts of the key
	unsigned char	key[20];
	for (uint16_t i=0; i<15; i++) {
		if (i==4 || i==9 || i==14) {
			key[i]='\0';
		} else {
			r.generateScaledNumber(0,9,&val);
			key[i]=val+'0';
		}
	}

	// generate the last part of the key
	unsigned char	*keypart1=key;
	unsigned char	*keypart2=key+5;
	unsigned char	*keypart3=key+10;
	unsigned char	l=lrc(keypart1,true,lrc100);
	key[15]=(l%10)+'0';
	if (debug) {
		debugdest->printf("lrc%10 of %s and %c: %c\n",
					keypart1,lrc100,key[15]);
	}

	l=lrc(keypart2,true,lrc10);
	key[16]=(l%10)+'0';
	if (debug) {
		debugdest->printf("lrc%10 of %s and %c: %c\n",
					keypart2,lrc10,key[16]);
	}

	l=lrc(keypart3,true,lrc1);
	key[17]=(l%10)+'0';
	if (debug) {
		debugdest->printf("lrc%10 of %s and %c: %c\n",
					keypart3,lrc1,key[17]);
	}

	key[18]=((key[15]+key[16]+key[17])%10);
	if (key[18]==5) {
		key[18]=4;
	}
	key[18]=key[18]+'0';
	if (debug) {
		debugdest->printf("sum(lrcs)%10 (5->4): %c\n",key[18]);
	}

	key[4]='-';
	key[9]='-';
	key[14]='-';
	key[19]='\0';

	if (debug) {
		debugdest->printf("product key: %s\n",key);

		debugdest->printf("======================="
					"=======================\n");
	}

	// print the key
	stdoutput.printf("%s\n",key);

	return true;
}

bool generateDateKey(const char *date, bool debug) {

	if (debug) {
		debugdest->printf("======================="
					"=======================\n");
		debugdest->printf("expiration date: %s\n",date);
	}

	// get the epoch time
	datetime	dt;
	dt.initialize(date);
	char	*epoch=charstring::parseNumber((uint64_t)dt.getEpoch());
	if (debug) {
		debugdest->printf("epoch time: %s\n",epoch);
	}

	size_t	epochlen=charstring::length(epoch);

	// init the random number generator
	randomnumber	r;
	r.setSeed(randomnumber::getSeed());

	// generate the rotation factor
	int32_t		rot;
	r.generateScaledNumber(2,9,&rot);
	if (debug) {
		debugdest->printf("rotation factor: %d\n",rot);
	}


	// rotate the epoch time
	for (size_t i=0; i<epochlen; i++) {
		epoch[i]=((epoch[i]-'0'+rot)%10)+'0';
	}
	if (debug) {
		debugdest->printf("rotated epoch time: %s\n",epoch);
	}


	// generate the first 3 parts of the key...
	unsigned char	key[20];
	bytestring::zero(key,sizeof(key));

	// first is the rotation factor
	key[0]=rot+'0';

	// now, all of the epoch time digits, backwards
	for (uint16_t i=1; i<15; i++) {

		// dashes
		if (i==4 || i==9 || i==14) {
			key[i]='\0';
			continue;
		}

		if (epochlen>0) {
			key[i]=epoch[epochlen-1];
			epochlen--;
		} else {
			key[i]=rot+'0';
		}
	}

	// generate the last part of the key
	unsigned char	*keypart1=key;
	unsigned char	*keypart2=key+5;
	unsigned char	*keypart3=key+10;
	unsigned char	l=lrc(keypart1,false,0);
	key[15]=(l%10)+'0';
	if (debug) {
		debugdest->printf("lrc%10 of %s: %c\n",keypart1,key[15]);
	}


	l=lrc(keypart2,false,0);
	key[16]=(l%10)+'0';
	if (debug) {
		debugdest->printf("lrc%10 of %s: %c\n",keypart2,key[16]);
	}


	l=lrc(keypart3,false,0);
	key[17]=(l%10)+'0';
	if (debug) {
		debugdest->printf("lrc%10 of %s: %c\n",keypart3,key[17]);
	}


	key[4]='-';
	key[9]='-';
	key[14]='-';
	key[18]='5';
	key[19]='\0';

	if (debug) {
		debugdest->printf("product key: %s\n",key);

		debugdest->printf("======================="
					"=======================\n");
	}


	// print the key
	stdoutput.printf("%s\n",key);

	return true;
}

bool generateDaysKey(const char *days, bool debug) {
	datetime	dt;
	dt.getSystemDateAndTime();
	dt.addDays(charstring::toInteger(days));
	return generateDateKey(dt.getString(),debug);
}

int main(int argc, const char **argv) {

	commandline	cmdl(argc,argv);

	// get debug from the command line
	bool	debug=cmdl.found("debug");

	// get name/days/date from the command line
	const char	*name=cmdl.getValue("name");
	const char	*days=cmdl.getValue("days");
	const char	*date=cmdl.getValue("date");
	uint32_t	quantity=charstring::toInteger(
					cmdl.getValue("quantity"));

	// init the debug destination
	debugdest=&stderror;

	// if that fails then try the PATH_INFO
	// (we may have been run as a CGI)
	char	*goodpi=NULL;
	if (charstring::isNullOrEmpty(name) &&
		charstring::isNullOrEmpty(days) &&
		charstring::isNullOrEmpty(date)) {

		const char	*pi=environment::getValue("PATH_INFO");
		if (!charstring::isNullOrEmpty(pi)) {

			// massage the path info
			while (*pi=='/') {
				pi++;
			}
			goodpi=charstring::httpUnescape(pi);

			// is it a date, days or name?
			if (charstring::toInteger(goodpi)) {
				if (charstring::contains(goodpi,"/") &&
					charstring::contains(goodpi," ") &&
					charstring::contains(goodpi,":")) {
					date=goodpi;
				} else {
					days=goodpi;
				}
			} else {
				name=goodpi;
			}

			// quantity?
			const char	*qs=
				environment::getValue("QUERY_STRING");
			const char	*q=NULL;
			if (!charstring::compare(qs,"quantity=",9)) {
				q=qs+9;
			} else {
				q=charstring::findFirst(qs,"&quantity=");
				if (q) {
					q+=10;
				}
			}
			if (q) {
				quantity=charstring::toInteger(q);
			}

			// debug?
			if (!charstring::compare(qs,"debug") ||
				!charstring::compare(qs,"debug&",6) ||
				charstring::findFirst(qs,"&debug")) {
				debug=true;
			}

			// we'll need a http header
			stdoutput.printf("Content-Type: text/plain\n\n");
			snooze::macrosnooze(3);

			// set the debug destination
			debugdest=&stdoutput;
		}
	}

	// massage the quantity
	if (!quantity) {
		quantity=1;
	}

	// generate the keys
	int32_t	status=0;
	if (!charstring::isNullOrEmpty(name)) {
		for (uint32_t i=0; i<quantity && !status; i++) {
			status=!generateNameKey(name,debug);
		}
	} else if (!charstring::isNullOrEmpty(days)) {
		for (uint32_t i=0; i<quantity && !status; i++) {
			status=!generateDaysKey(days,debug);
		}
	} else if (!charstring::isNullOrEmpty(date)) {
		for (uint32_t i=0; i<quantity && !status; i++) {
			status=!generateDateKey(date,debug);
		}
	} else {
		stdoutput.printf("usage: fwpk "
			"[-name product-name] "
			"[-days number-of-days] "
			"[-date \"MM/DD/YYYY HH:MM:SS\"]\n");
		status=1;
	}

	// clean up
	delete[] goodpi;

	// exit
	process::exit(status);
}
